package com.opensymphony.xwork2.ejb3plugin;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ejb3plugin.cache.AnnotatedField;
import com.opensymphony.xwork2.ejb3plugin.cache.InjectEJBCache;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * Looks for any fields annotated with the InjectEJB annotation and injects the
 * given EJB into that field.
 */
public class InjectEJBInterceptor extends AbstractInterceptor implements Interceptor {

	private static final long serialVersionUID = -2078783172069592011L;
	
	private final static String LOCAL = "/local";
	private final static String REMOTE = "/remote";
	
	// cache of annotated fields
	private InjectEJBCache cache;
	
	
	public InjectEJBInterceptor() {
		super();
		this.cache = InjectEJBCache.getInstance();
	}
	
	
	public String intercept(ActionInvocation actionInvocation) throws Exception {
		
		Object action = actionInvocation.getAction();
		String actionClassName = action.getClass().getName();
		
		Boolean hasEJBAnnotations = this.cache.hasEJBAnnotations(actionClassName);
		if (Boolean.TRUE.equals(hasEJBAnnotations)) {
		// hit the cache
		//	
			List<AnnotatedField> aFields = this.cache.getAnnotatedFields(action.getClass().getName());
			for (Iterator<AnnotatedField> it = aFields.iterator(); it.hasNext();) {
				this.injectEJB(action, it.next());
			}
		}
		else if (hasEJBAnnotations == null) {
		// unknown yet
		//
			List<AnnotatedField> annotatedFields = new ArrayList<AnnotatedField>();
			
			for (Field f : action.getClass().getDeclaredFields()) {
				
				if (f.isAnnotationPresent(InjectEJB.class)) {
				// found EJBInject annotations
					
					AnnotatedField aField = new AnnotatedField((InjectEJB) f.getAnnotation(InjectEJB.class), f);
					this.injectEJB(action, aField);
					annotatedFields.add(aField);
				}
			}		
			
			// cache data
			if (annotatedFields.size() == 0) {
				cache.noEJBAnnotations(actionClassName);
			}
			else {
				cache.cacheAnnotatedFields(actionClassName, annotatedFields);
			}
		}
		
		return actionInvocation.invoke();
	}
	
	
	private void injectEJB(Object action, AnnotatedField aField) throws Exception {

		InjectEJB annotation = aField.getAnnotation();
		Field f = aField.getField();

		// determine service name
		StringBuilder serviceName = new StringBuilder(annotation.name() != null ? annotation.name() : f.getType().getName());
		if (annotation.local()) {
			serviceName.append(LOCAL);
		}
		else if (annotation.remote()) {
			serviceName.append(REMOTE);
		}

		// try to access the service from the (global) JNDI
		Object service = null;
		InitialContext ic = new InitialContext();
		try {
			service = ic.lookup(serviceName.toString());
		}
		catch (NamingException ex) {
			serviceName.insert(0, annotation.appname()+"/");
			service = ic.lookup(serviceName.toString());
		}
		finally {
			if (service != null) {
				boolean wasAccessible = f.isAccessible();
				f.setAccessible(true);
				f.set(action, service);
				f.setAccessible(wasAccessible);
			}
		}
	}
	
}
